from dataclasses import dataclass, field
from decimal import Decimal

from administrators.models import Userinfo
from utils.tools import ParamCheck
import psycopg2
from django.db import IntegrityError, transaction
from django.db.models import CharField, Sum
from django.db.models.functions import Cast
import json
from administrators.views.token_check import verify_token  # 注意，这个是从管理应用引入的
from django.views.decorators.http import require_http_methods
from utils.enums import SCE
from utils.result import R
from datetime import date
import logging
# ---------------------------------------------------------------------
from inventory.models import MaterialGroup, Material

logger = logging.getLogger('inventory')


@dataclass  # 停用和启用 的请求参数
class Type1:
    id: int


@dataclass  # 物料分组crud 的请求参数
class MaterialGroupCreate:
    mnemonic: str
    name: str
    operator: Userinfo
    enable: bool
    operate_date: date = field(default_factory=lambda: date.today())


@dataclass  # 物料分组crud 的请求参数
class MaterialGroupUpdate:
    id: int
    mnemonic: str
    name: str
    operator: Userinfo
    operate_date: date = field(default_factory=lambda: date.today())


# 某个物料分类的停用和启用
@verify_token()
@require_http_methods(['POST'])
def material_group_deactivation_and_activation(request, user_id: str):
    data = Type1(id=ParamCheck.int(json.loads(request.body)['id']))
    # 物料分组停用的条件：该分组下的所有物料，数量都归零，重量可以不为零
    try:
        with transaction.atomic():
            obj = MaterialGroup.objects.get(id=data.id)
            use = not obj.enable  # 希望调整为的状态
            if use:  # 如果希望启用该分组
                obj.enable = True
                obj.save()  # 启用分组
                temp1 = Material.objects.filter(group=data.id)
                for i in temp1:
                    i.enable = True
                Material.objects.bulk_update(temp1, fields=['enable'])  # 启用该分组下的所有物料
                return R.success('物料分组已经启用').data()
            else:  # 如果希望关闭该分组
                # 检查该分组下的未关闭的物料是否均满足关闭条件，如果均满足条件，那么关闭物料分组的同时，将其下物料全部关闭
                temp2 = Material.objects.filter(group=data.id, enable=True)
                for i in temp2:
                    if i.enable:  # 如果发现某个物料没有关闭
                        # 如果这个物料不具备关闭的条件
                        if i.whole_piece != 0 or i.scatt_piece != 0:
                            return R.error(SCE.no_enum, f'物料分组停用失败，编码为 {i.code} 的物料件数和支数不为零').data()
                        # 具备条件则将其关闭
                        else:
                            i.enable = False
                            i.save()
                obj.enable = False  # 停用分组
                obj.save()
                Material.objects.bulk_update(temp2, fields=['enable'])  # 停用该分组下的所有物料
                return R.success('物料分组已经停用').data()

    except MaterialGroup.DoesNotExist:
        return R.error(SCE.no_enum, '要操作的物料分组不存在').data()
    except KeyError:  # 为dataclass赋值错误
        return R.error(SCE.no_enum, '请求参数错误').data()


# 某个物料的停用和启用
@verify_token()
@require_http_methods(['POST'])
def material_deactivation_and_activation(request, user_id: str):
    data = Type1(id=ParamCheck.int(json.loads(request.body)['id']))
    # 如果该物料所属分组处于关闭状态，那么其下的物料状态不得变更，关闭物料需要件数为0
    try:
        obj = Material.objects.get(id=data.id)
        temp1 = obj.group
        if not temp1.enable:
            return R.error(SCE.no_enum, '物料所属分组处于关闭状态，物料状态不可更改').data()

        use = not obj.enable  # 希望调整为的状态

        if use:  # 如果希望启用该物料
            obj.enable = True
            obj.save()

            return R.success('物料已经启用').data()
        else:  # 如果希望关闭该物料
            if obj.whole_piece != 0 or obj.scatt_piece != 0:
                return R.error(SCE.no_enum, '物料停用失败，尚有物料未清空').data()
            else:
                obj.enable = False
                obj.save()
                return R.success('物料已经停用').data()

    except Material.DoesNotExist:
        return R.error(SCE.no_enum, '要操作的物料不存在').data()
    except KeyError:  # 为dataclass赋值错误
        return R.error(SCE.no_enum, '请求参数错误').data()


# 物料分类增删改查
@verify_token()
@require_http_methods(['POST'])
def material_group_crud(request, user_id: str):
    data = json.loads(request.body)
    user_obj = Userinfo.objects.get(id=user_id)
    match data['mode']:
        case 'create':
            mnemonic = ParamCheck.str(data['param']['mnemonic'], length=(1, 100))
            name = ParamCheck.str(data['param']['name'], length=(1, 100))
            try:
                with transaction.atomic():
                    MaterialGroup(mnemonic=mnemonic,
                                  name=name,
                                  operator_id=user_id,
                                  enable=True,
                                  operate_date=date.today(),
                                  weight_fix=0).save()
                    return R.success('物料分组创建成功').data()
            except IntegrityError as e:
                if isinstance(e.__cause__, psycopg2.errors.UniqueViolation):
                    return R.error(SCE.no_enum, '物料分组编码或名称重复').data()
            except Userinfo.DoesNotExist:
                return R.error(SCE.UserNotExist).data()

        case 'delete':
            # 该分类处于关闭状态，且其下物料均被删除后，其本身可被删除
            material_group_id=ParamCheck.int(data['param']['id'])
            obj = MaterialGroup.objects.get(id=material_group_id)
            if obj.enable:
                return R.error(SCE.no_enum, '物料分组未处于关闭状态，不能删除').data()
            if Material.objects.filter(group=obj).exists():
                return R.error(SCE.no_enum, '该物料分组下尚有物料未被删除').data()
            logger.info(f'物料分组删除成功，名称：{obj.name}，id：{obj.id}，用户：{user_obj.username}')
            obj.delete()
            return R.success('物料分组删除成功').data()

        case 'update':
            # 不能更新一个处于关闭状态的分组
            material_group_id = ParamCheck.int(data['param']['id'])
            mnemonic = ParamCheck.str(data['param']['mnemonic'], length=(1, 100))
            name = ParamCheck.str(data['param']['name'], length=(1, 100))
            weight_fix = ParamCheck.decimal(data['param']['weight_fix'])

            obj = MaterialGroup.objects.get(id=material_group_id)
            if not obj.enable:
                return R.error(SCE.no_enum, '物料分组处于关闭状态，不能修改').data()
            else:
                obj.mnemonic = mnemonic
                obj.name = name
                obj.operator_id = user_id
                obj.weight_fix = weight_fix
                obj.operate_date = date.today()
                obj.save()
                return R.success('物料分组修改成功').data()

        case 'read':
            material_group_id = ParamCheck.int(data['param']['id'])
            result = (MaterialGroup.objects
                      .filter(id=material_group_id)
                      .values('id', 'mnemonic', 'name', 'enable', 'operator__username', 'weight_fix',
                              operate_date_=Cast('operate_date',CharField())))
            temp1 = (Material.objects
                     .filter(group_id=material_group_id)
                     .values('group')
                     .annotate(weight=Sum('weight'), theory_weight=Sum('theory_weight')))
            result[0]['weight'] = temp1[0]['weight'] + result[0]['weight_fix']
            result[0]['theory_weight'] = temp1[0]['theory_weight']

            return R.success('物料分组读取成功').data({result: result})
        case _:
            return R.error(SCE.ParamError).data()


# 物料分类模糊查询
@require_http_methods(['POST'])
@verify_token()
def material_group_fuzzy_search(request, user_id: str):
    # 根据是否启用，助记码，名称进行模糊搜索
    data = json.loads(request.body)
    enable = ParamCheck.str(data.get('enable'))
    name = ParamCheck.str(data.get('name'), length=(1, 100), force=False)
    mnemonic = ParamCheck.str(data.get('mnemonic'), length=(1, 100), force=False)
    start = ParamCheck.int(data.get('start'))
    stop = ParamCheck.int(data.get('stop'))

    result = MaterialGroup.objects
    if enable == 'enable':
        result = result.filter(enable=True)
    if enable == 'not_enable':
        result = result.filter(enable=False)
    if name is not None:
        result = result.filter(name__contains=name)
    if mnemonic is not None:
        result = result.filter(mnemonic__contains=mnemonic)

    result = result.values('id', 'mnemonic', 'name', 'enable', 'weight_fix', 'operator__username',
                           operate_date_=Cast('operate_date', CharField()))

    total = result.count()
    result = list(result[start: stop])

    for item in result:
        temp1 = (Material.objects
                 .filter(group_id=item['id'])
                 .values('group')
                 .annotate(weight=Sum('weight'), theory_weight=Sum('theory_weight'), whole_piece=Sum('whole_piece')))
        if len(temp1) == 1:
            item['weight'] = temp1[0]['weight'] + item['weight_fix']
            item['theory_weight'] = (temp1[0]['theory_weight'] * Decimal('0.001')).quantize(Decimal('0.001'))
            item['whole_piece'] = temp1[0]['whole_piece']
        else:
            item['weight'] = 0 + item['weight_fix']
            item['theory_weight'] = 0
            item['whole_piece'] = 0

    return R.success('物料分组模糊搜索成功').data({'result': list(result), 'total': total})


# 物料分类模糊查询，只返回名称和助记码
@require_http_methods(['POST'])
@verify_token()
def material_group_name_only_fuzzy_search(request, user_id: str):
    pass
